// eee.Sheffield.PZ.Imaging
//
// Copyright ?Ping Zou, 2006
// sg71.cherub@gmail.com


using System;
using System.Collections.Generic;
using System.Text;using eee.Sheffield.PZ.Math;
using System.Drawing;
using System.Drawing.Imaging;

namespace eee.Sheffield.PZ.Imaging.ImageFilter
{
    public enum FloodFillConditionType { Threshold, Tolerance, AdaptiveThreshold, AdaptiveTolerance}
    public enum FloodFillType { Iterative, Linear }
    public class FloodFill: ImageFilter//, IGraylevelImageFilter
    {
        #region Fields
        private PZPoint _startPoint = new PZPoint(0, 0);
        private FloodFillConditionType _floodFillConditionType = FloodFillConditionType.Tolerance;
        private FloodFillType _floodFillType = FloodFillType.Iterative;        
        private double _condition = 2.0;
        private double _fillColour = 0.0;
        //private string _flag = "[Image -> Threshold -> Flood Fill]";
        #endregion

        #region Properties
        //public string Flag() { return _flag; }
        //public string Log() { return _log; }
        public PZPoint StartPoint { get { return _startPoint; } set { _startPoint = value; } }
        public FloodFillConditionType FloodFillConditionType { get { return _floodFillConditionType; } set { _floodFillConditionType = value; } }
        public FloodFillType FloodFillType { get { return _floodFillType; } set { _floodFillType = value; } }
        public double Condition { get { return _condition; } set { _condition = value; } }
        public double FillColour { get { return _fillColour; } set { _fillColour = value; } }
        #endregion

        #region Constructor
        public FloodFill()
        {
            _flag = "[Image -> Threshold -> Flood Fill]";
        }
        #endregion

        #region Apply filter
        // Since return a colour result though process a graylevel image
        // do not use base class's interface methods.
        public new Bitmap ApplyGraylevelImageFilter(Bitmap srcImage) 
        { 
            return ApplyGraylevelImageFilter(srcImage, out _log); 
        }
        public new PZMath_matrix ApplyDoubleGraylevelImageFilter(PZMath_matrix srcMatrix) 
        { 
            return ApplyDoubleGraylevelImageFilter(srcMatrix, out _log); 
        }
        protected new Bitmap ApplyGraylevelImageFilter(Bitmap srcImage, out string logText)
        {
            PZMath_matrix srcMatrix = _converter.ConvertGraylevelBitmapToMatrix(srcImage);
            PZMath_matrix dstMatrix = ApplyDoubleGraylevelImageFilter(srcMatrix, out logText);
            Bitmap dstImage = ConvertBitmapMatrix.ToBitmap(dstMatrix, Color.Red);
            return dstImage;
        }
        protected override PZMath_matrix ApplyDoubleGraylevelImageFilter(PZMath_matrix srcMatrix, out string logText)
        {
            // prepare srcMatrix and dstMatrix            
            int height = srcMatrix.RowCount;
            int width = srcMatrix.ColumnCount;
            PZMath_matrix dstMatrix = new PZMath_matrix(height, width);
            // set dstMatrix white background
            dstMatrix.Setall(255.0);

            // prepare pixelChecked matrix
            PZMath_matrix pixelCheckedMatrix = new PZMath_matrix(height, width);
            pixelCheckedMatrix.Setall(0.0);

            // flood fill
            double _startColour = srcMatrix[(int)_startPoint.y, (int)_startPoint.x];
            if (_floodFillType == FloodFillType.Iterative)
                IterativeFloodFill(_startPoint, _fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    _floodFillConditionType, _condition, _startColour);

            // convert dstMatrix to a bitmap, filling red colour
            logText = "Start at (" + (int)_startPoint.x + ", " + (int)_startPoint.y + ")";
            return dstMatrix;
        } // ApplyDoubleGraylevelImageFilter()

        protected override PZMath_matrix[] ApplyDoubleColourImageFilter(PZMath_matrix[] srcMatrixArray, out string logText)
        {
            // TODO: add this function
            
            throw new Exception("The method or operation is not implemented.");
        } // ApplyDoubleColourImageFilter()
        #endregion

        #region flood fill
        /// <summary>
        /// iterative flood fill
        /// </summary>
        void IterativeFloodFill(PZPoint currentPoint, double fillColour,
            ref PZMath_matrix srcMatrix, ref PZMath_matrix dstMatrix,
            ref PZMath_matrix pixelCheckedMatrix, FloodFillConditionType floodFillConditionType,
            double condition, double startColour)
        {
            // get dimensions
            int height = srcMatrix.RowCount;
            int width = srcMatrix.ColumnCount;

            // fill and mark current point
            dstMatrix[(int)currentPoint.y, (int)currentPoint.x] = fillColour;
            pixelCheckedMatrix[(int)currentPoint.y, (int)currentPoint.x] = 1.0;

            // move to neighbours

            // up - left neighbour
            PZPoint upLeftNeightbourPoint = new PZPoint(currentPoint.x - 1, currentPoint.y - 1);
            if (upLeftNeightbourPoint.x > 0 && upLeftNeightbourPoint.y > 0
                && CheckNeighbour(currentPoint, upLeftNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)upLeftNeightbourPoint.y, (int)upLeftNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(upLeftNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }

            // up neighbour
            PZPoint upNeightbourPoint = new PZPoint(currentPoint.x, currentPoint.y - 1);
            if (upNeightbourPoint.y > 0
                && CheckNeighbour(currentPoint, upNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)upNeightbourPoint.y, (int)upNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(upNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }
            // up - right neighbour
            PZPoint upRightNeightbourPoint = new PZPoint(currentPoint.x + 1, currentPoint.y - 1);
            if (upRightNeightbourPoint.x < width && upRightNeightbourPoint.y > 0
                && CheckNeighbour(currentPoint, upRightNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)upRightNeightbourPoint.y, (int)upRightNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(upRightNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }

            // left neighbour
            PZPoint leftNeightbourPoint = new PZPoint(currentPoint.x - 1, currentPoint.y);
            if (leftNeightbourPoint.x > 0
                && CheckNeighbour(currentPoint, leftNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)leftNeightbourPoint.y, (int)leftNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(leftNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }
            // right neighbour
            PZPoint rightNeightbourPoint = new PZPoint(currentPoint.x + 1, currentPoint.y);
            if (rightNeightbourPoint.x < width
                && CheckNeighbour(currentPoint, rightNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)rightNeightbourPoint.y, (int)rightNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(rightNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }
            // bottom - left neighbour
            PZPoint bottomLeftNeightbourPoint = new PZPoint(currentPoint.x - 1, currentPoint.y + 1);
            if (bottomLeftNeightbourPoint.x > 0 && bottomLeftNeightbourPoint.y < height
                && CheckNeighbour(currentPoint, bottomLeftNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)bottomLeftNeightbourPoint.y, (int)bottomLeftNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(bottomLeftNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }
            // bottom neighbour
            PZPoint bottomNeightbourPoint = new PZPoint(currentPoint.x, currentPoint.y + 1);
            if (bottomNeightbourPoint.y < height
                && CheckNeighbour(currentPoint, bottomNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)bottomNeightbourPoint.y, (int)bottomNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(bottomNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }
            // bottom - right neighbour
            PZPoint bottomRightNeightbourPoint = new PZPoint(currentPoint.x + 1, currentPoint.y + 1);
            if (bottomRightNeightbourPoint.x < width && bottomRightNeightbourPoint.y < height
                && CheckNeighbour(currentPoint, bottomRightNeightbourPoint, ref srcMatrix, condition, floodFillConditionType, startColour)
                && pixelCheckedMatrix[(int)bottomRightNeightbourPoint.y, (int)bottomRightNeightbourPoint.x] != 1.0)
            {
                IterativeFloodFill(bottomRightNeightbourPoint, fillColour, ref srcMatrix, ref dstMatrix, ref pixelCheckedMatrix,
                    floodFillConditionType, condition, startColour);
            }
        }
        #endregion

        #region utility functions
        private bool CheckNeighbour
            (PZPoint currentPoint, PZPoint neighbourPoint, ref PZMath_matrix srcMatrix, 
            double condition, FloodFillConditionType floodFillConditionType, double startColour)
        {
            if (floodFillConditionType == FloodFillConditionType.Threshold
                || floodFillConditionType == FloodFillConditionType.AdaptiveThreshold)
                return CheckNeighbourThreshold(currentPoint, neighbourPoint, ref srcMatrix, condition);
            else if (floodFillConditionType == FloodFillConditionType.Tolerance)
                return CheckNeighbourTolerance(neighbourPoint, ref srcMatrix, condition, startColour);
            else
                return CheckNeighbourAdaptiveTolerance(currentPoint, neighbourPoint, ref srcMatrix, condition);
        }
        private bool CheckNeighbourThreshold
            (PZPoint currentPoint, PZPoint neighbourPoint,
             ref PZMath_matrix srcMatrix, double condition)
        {
            if (srcMatrix[(int)neighbourPoint.y, (int)neighbourPoint.x] >= condition)
                return true;
            else
                return false;
        }
        private bool CheckNeighbourAdaptiveTolerance
            (PZPoint currentPoint, PZPoint neighbourPoint,
             ref PZMath_matrix srcMatrix, double condition)
        {
            double diff = System.Math.Abs(srcMatrix[(int)currentPoint.y, (int)currentPoint.x] - srcMatrix[(int)neighbourPoint.y, (int)neighbourPoint.x]);
            if (diff <= condition)
                return true;
            else
                return false;
        }
        private bool CheckNeighbourTolerance
            (PZPoint neightbourPoint, ref PZMath_matrix srcMatrix, double condition, double startColour)
        {
            double diff = System.Math.Abs(srcMatrix[(int)neightbourPoint.y, (int)neightbourPoint.x] - startColour);
            if (diff <= condition)
                return true;
            else
                return false;
        }
        #endregion

        #region Example
        #endregion
    }
}
